// Copyright (c) 2008 Matthew Johnson
// Licensed under the MIT License (see license.txt)
package actionml.interaction {
	import actionml.Global;
	
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.events.AsyncErrorEvent;
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.NetStatusEvent;
	import flash.events.SecurityErrorEvent;
	import flash.media.Video;
	import flash.net.NetConnection;
	import flash.net.NetStream;
	import flash.net.Responder;	
	import flash.text.TextField;
	import flash.utils.setTimeout;
	import flash.utils.clearTimeout;
	
	public class VideoInteractions {
		public static function initialize() {
			Global.initializeExtension(VideoInteractions, DisplayObject);
		}
		public static var instanceMethods:Object = {
			makeVideo: function(options:Object = null) {
				if (this.isAlready("Video")) return;
				var o:DisplayObject = this;
				this.titleText = this.findChild("titleText", options);
				if (this.titleText) {
					this.getTitle = function() {
						return this.titleText.text;
					};
					this.setTitle = function(title:String) {
						this.titleText.text = title;
					};
				}
				if (options["title"]) {	
					if (this.titleText) {
						this.setTitle(options["title"]);
					} else {
						// TODO: trace no title text error
					}
				}
				var bufferingIndicator:DisplayObject = this.getChildByName(options["bufferingIndicator"] || "bufferingIndicator");
				bufferingIndicator.visible = false;
				bufferingIndicator.play();
				this.bufferTime = .01;
				if (options && options["bufferTime"]) {
					this.bufferTime = options["bufferTime"];
				}
				this.playMedia = function() {
					if (!this.baseURL || !this.streams || this.streams.length < 1) {
						// TODO: trace error
						return;
					}
					if (this.connection && this.stream && this.videoPlayer) {
						if (this.paused && !this.atEnd) {
							this.stream.resume();
						} else {
							this.stream.play(this.streamName, 0);
						}
					} else {
						this.connectToVideo();
					}
					bufferingIndicator.visible = true;
					bufferingIndicator["moveToFront"]();
					this.playing = true;
					this.paused = false;
					this.atEnd = false;
					this.doEvent("play");
				};
				this.stopMedia = function() {
					if (this.stream) {
						bufferingIndicator.visible = false;
						this.playing = false;
						this.paused = true;
						this.stream.pause();
						this.doEvent("pause");
					}
				};
				this.getBeginningLocation = function() {
					return 0;
				};
				this.getEndingLocation = function() {
					return this.length || 0; 
				};
				this.getLocation = function() {
					if (this.stream) {
						if (this.metadata && this.metadata.audiodelay) {
							if (this.stream.time - this.metadata.audiodelay > 0) {
								return this.stream.time - this.metadata.audiodelay;
							}
						} else {
							return this.stream.time;
						}
					}
					return 0;
				};
				this.setLocation = function(l:Number) {
					if (this.stream) {
						this.stream.seek(l);
						if (!this.playing) {
							this.stream.pause();
							this.paused = true;
							this.atEnd = false;
						}
					}
				};
				this.netStatusHandler = function(e:NetStatusEvent) {
					if (e.info.level == "error") {
						/*
						NetStream.Failed
						NetStream.Play.Failed
						NetStream.Play.StreamNotFound
						NetStream.Seek.Failed
						NetConnection.Call.Failed
						NetConnection.Connect.Failed
						NetConnection.Connect.InvalidApp
						NetConnection.Connect.Rejected
						*/
						o.handleError(e);
					}
					if (e.info.code == "NetStream.Play.Start" || (e.info.code == "NetStream.Buffer.Empty" && o.playing)) {
						if (o.playing) {
							o.bufferTimeout = setTimeout(function() {
								bufferingIndicator.visible = true;
								bufferingIndicator["moveToFront"]();
								o.doEvent("buffering");
							}, 100); // wait 100 ms before showing the buffering indicator
							bufferingIndicator.visible = true;
							bufferingIndicator["moveToFront"]();
							o.doEvent("buffering");
						}
						/*
						o.bufferCount ||= 0;
						o.bufferCount++;
						trace(o.bufferCount + " --------------------");*/
					}
					if (e.info.code == "NetStream.Buffer.Full") {
						if (o.bufferTimeout) {
							clearTimeout(o.bufferTimeout);
						}
						o.bufferTimeout = null;
						bufferingIndicator.visible = false;
						o.doEvent("bufferingComplete");
					}
					if (e.info.code == "NetStream.Buffer.Flush") {	
						if (o.getEndingLocation() - o.getLocation() < .5) { // floating point ==
							if (o.bufferTimeout) {
								clearTimeout(o.bufferTimeout);
							}
							o.bufferTimeout = null;
							bufferingIndicator.visible = false;
							o.playing = false;
							o.paused = false;
							o.atEnd = true;
							o.doEvent("streamComplete");
						}
					}
				};
				this.connectionErrorHandler = function(e:Event) {
					o.handleError(e);
				};
				this.streamErrorHandler = function(e:Event) {
					o.handleError(e);
				};
				this.handleError = function(e:Event) {
					trace(e) // todo: better trace
					this.doEvent("error");
					this.disconnectFromVideo();
				};
				this.takeSource(options || this, true);
			},
			playSource: function(o:Object) {
				this.takeSource(o);
				this.playMedia();
			},
			takeSource: function(o:Object, isInitializing:Boolean=false) {
				this.disconnectFromVideo(); 
				// regex to parse url into components used by Flash's NCMananger -- we don't really need to do this, but it's here if we ever do.
				//var s:Object = o["src"].match(/^(?P<protocol>[A-Za-z]+:\/)\/(?P<server>[A-Za-z0-9_\-.]+)(:(?P<port>\d+))?\/(?P<path>[A-Za-z0-9_\-.]+(\/[A-Za-z0-9_\-.]+)*)\/(?P<file>[A-Za-z0-9_\-.]*[A-Za-z0-9])$/);
				this.baseURL = o["URL"]
				this.streams = [];
				for (var s:String in o) {
					if (s["startsWith"]("source")) {
						this.streams.push({bitrate: s.replace(/^source/, ""), stream: String(o[s]).replace(/\.flv$/, "")});
					}
				}
				if (this.baseURL && this.streams.length > 0) {
					this.connectToVideo();
				} else if (!isInitializing) { // it 
					// TODO: trace warning
				}
			},

			connectToVideo: function() {
				if (!this.connection) {
					// timer should be set up to timeout the connection if it fails
					this.connection = new NetConnection();
					this.connection.client = this; 
					this.connection.objectEncoding = 0;
					this.connection.proxyType = "best";
					this.connection.addEventListener(NetStatusEvent.NET_STATUS, this.netStatusHandler);
					this.connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, this.connectionErrorHandler);
					this.connection.addEventListener(IOErrorEvent.IO_ERROR, this.connectionErrorHandler);
					this.connection.addEventListener(SecurityErrorEvent.SECURITY_ERROR, this.connectionErrorHandler);
					this.connection.connect(this.baseURL, true); // calls back to onBWDone
					//this.connection.connect(null); // for http if we need to add support for this.
				}
			},
			onBWCheck: function() {
				return (arguments && arguments.length > 1 ? arguments[1] : 0);
			},
			onBWDone: function() {
				this.bitrate = arguments[0];
				this.streamName = "";
				this.streamRate = 0;
				for each (var s:Object in this.streams) {
					if (Number(s.bitrate) >= this.streamRate && Number(s.bitrate) < this.bitrate) {
						this.streamRate = Number(s.bitrate);
						this.streamName = s.stream;
					}
				}
				var o:DisplayObject = this;
				this.connection.call("getStreamLength", new Responder(
					function(length:Number) {
						if (!o.length) {
							o.length = length;
							o.doEvent("streamInfo");
						}
					}, 
					function() {trace("getStreamLength error: " + arguments)}), 
					this.streamName);
				this.stream = new NetStream(this.connection);
				this.stream.client = this;
				this.stream.bufferTime = this.bufferTime;
				this.stream.addEventListener(NetStatusEvent.NET_STATUS, this.netStatusHandler);
				this.stream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, this.connectionErrorHandler);
				this.stream.addEventListener(IOErrorEvent.IO_ERROR, this.connectionErrorHandler);
				this.videoPlayer = new Video(this.width, this.height);
				this.videoPlayer.attachNetStream(this.stream);
				this.videoPlayer.visible = true;
				this.addChild(this.videoPlayer);
				// move the video behind movie clips and text fields, but 
				// leave other shapes / images, etc behind it (probably used as a placeholder)
				this.videoPlayer["moveToBack"]();
				for (var i = (this["numChildren"] - 1); i >= 0 ; i--) {
					var child:DisplayObject = this.getChildAt(i);
					if (!(child is TextField || child is MovieClip || child is Video)) {
						child["moveToBack"]();
					}
				}
				if (this.playing) {
					this.stream.play(this.streamName, this.getLocation());
				}
			},
			onMetaData: function() {
				if (arguments[0]) {
					this.metadata = arguments[0];
					if (this.metadata.duration) {
						if (!this.length) {
							this.length = this.metadata.duration;	
							this.doEvent("streamInfo");
						}
					}
				}
			},
			onCuePoint: function() {
				trace("onCuePoint " + arguments);
			},
			disconnectFromVideo: function() {
				// can be reused for http or if all except stream are same for rtmp
				this.playing = false;
				this.paused = false;
				this.atEnd = false;
				this.baseURL = null;
				this.streams = null;
				this.streamName = null;
				this.metadata = null;
				this.streamRate = null;
				this.bitrate = null;
				this.length = null;
				this.bufferTimeout = null;
				if (this.videoPlayer) {
					this.removeChild(this.videoPlayer);
					this.videoPlayer = null;
				}
				if (this.stream) {
					this.stream.close();
					this.stream.removeEventListener(NetStatusEvent.NET_STATUS, this.netStatusHandler);
					this.stream.removeEventListener(IOErrorEvent.IO_ERROR, this.streamErrorHandler);
					this.stream.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, this.streamErrorHandler);
					this.stream = null;
				}
				if (this.connection) {
					this.connection.close();
					this.connection.removeEventListener(NetStatusEvent.NET_STATUS, this.netStatusHandler);
					this.connection.removeEventListener(IOErrorEvent.IO_ERROR, this.connectionErrorHandler);
					this.connection.removeEventListener(AsyncErrorEvent.ASYNC_ERROR, this.connectionErrorHandler);
					this.connection.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, this.connectionErrorHandler);	
					this.connection = null;	
				}
			}
		}
	}
}